<?php
/**
 * <Class Description>.
 *
 * @author Matias Mirabelli <project@moyrax.com>
 *
 * @package Kingorm
 */
abstract class CatalogBase extends CModelEvents
{
	public $name = "";
	public $username = "";
	public $password = "";
	public $catalog = "";
	public $server = "";
	public $port = 0;

	public $lockTables = true;

	protected $hdb = null;
	protected $tables = array();
	protected $namedQuery = array();
	protected $onConnect = null;

	protected $DAO_DIR = "";

	protected static $instance = null;

	private static $TBL_DAO = "tblDao";
	private static $TBL_OBJ = "tblObj";
	private static $QRY_DAO = "qryDao";
	private static $QRY_STM = "qryStr";
	private static $QRY_TOK = "qryTok";


	/**
	 * Crea un nuevo objeto de acceso a datos (DAO) y lo vincula
	 * a una propiedad del esquema. Buscará las clases en el
	 * directorio DAO definido en la configuración.
	 *
	 * @param daoName       Nombre de la clase del DAO.
	 * @param propertyName  Nombre de la propiedad que se creará para este objeto.
	 */
	final public function DAORegister($daoName, $propertyName = null)
	{
		if ( !ClassLoader::getInstance()->loadClass( $daoName ) ) {
			return(null);
		}

		// Crea el objeto DAO y lo vincula
		// a esta conexión.
		//
		$class = new $daoName($this);

		if ( !isset($propertyName) )
			$propertyName = $daoName;

		$class->name = $daoName;
		$this->$propertyName = $class;

		// Las propiedades son de sólo-lectura.
		//
		$this->setPropReadOnly($propertyName);

		return( $this->$propertyName );
	}

	/**
	 * Vincula un DAO a una tabla de la base de datos. Las
	 * tablas deben estar definidas en el directorio de
	 * estructuras. Serán usadas en las llamadas al modelo.
	 *
	 * @param dao       Objeto DAO relacionado.
	 * @param tableName Nombre de la tabla a asociar.
	 */
	final public function tableRegister(DAOTemplate $dao, $tableName)
	{
		if ( $this->lockTables && $this->tableIsRegistered($dao, $tableName) )
			return(null);

		if ( !ClassLoader::getInstance()->loadClass( $tableName ) ) {
			return(null);
		}

		// Crea el objeto DAO y lo vincula
		// a esta conexión.
		//
		@$class = new $tableName($this);

		if ( !$class )
			return(null);

		$this->tables[$dao->name][$tableName] = $class;

		return($class);
	}

	final protected function tableIsRegistered($dao, $tableName)
	{
		return(
			isset( $this->tables[$dao->name] ) &&
			isset( $this->tables[$dao->name][$tableName] )
		);
	}

	/**
	 * Devuelve la tabla requerida vinculada al DAO, o todas
	 * las tablas asociadas al DAO especificado.
	 *
	 * @param dao       DAO vinculado a esta consulta.
	 * @param tableName Nombre de la tabla asociada al DAO.
	 */
	final protected function getTable(DAOTemplate $dao, $tableName = "")
	{
		if ( is_string($tableName) )
		{
			if ( !$this->tableIsRegistered($dao, $tableName) )
				return(null);

			$table = $this->tables[$dao->name][$tableName];

			if (  !isset($table) )
				return(null);

			return( $table );
		}
		else {
			$table = array();
			$daoTable = $this->tables[$dao->name];

			foreach ( array_keys($daoTable) as $item )
			{
				if ( $item == $tableName )
				{
					array_push( $table, $daoTable[$item] );
				}
			}

			if ( count($table) == 1 )
				return( $table[0] );
			else
				return( $table );
		}
	}

	/**
	 * Abre una conexión a esta base de datos.
	 *
	 * @param link Objeto Link vinculado a esta conexión (opcional).
	 */
	final public function connect($link = null)
	{
		if ( !isset($link) )
			$link = Link::getInstance();

		$this->hdb = $link->open($this);

		if ( !$this->hdb )
			$link->error($this, "No se pudo conectar a la base de datos.");

		$this->onConnect();
	}

	/**
	 * Devuelve una consulta SQL asociada al DAO especificado.
	 * Las consultas podrán ser vinculadas a los DAO al crearse
	 * un nuevo objeto Catalog.
	 *
	 * @param dao       Objeto DAO relacionado con la consulta.
	 * @param queryName Nombre de la consulta a recuperar.
	 */
	final public function getNamedQuery($dao, $queryName)
	{
		if ( !$dao )
			return(null);

		$query = $this->namedQuery[$queryName];

		if ( !isset($query) )
			return(null);

		if ( $query[self::$QRY_DAO] !== $dao )
			return(null);

		return( $query[self::$QRY_STM] );
	}

	final public function getQueryToken($dao, $queryName)
	{
		$query = $this->getNamedQuery($dao, $queryName);

		if ( !$query )
			return(null);

		$token = $this->namedQuery[$queryName][self::$QRY_TOK];

		if ( !isset($token) )
			return(null);

		return( $token );
	}

	/**
	 * Crea una consulta SQL con nombre y la vincula
	 * a un objeto DAO, para ser usada sólo por este.
	 *
	 * @param dao       Objeto DAO relacionado con esta consulta.
	 * @param queryName Nombre de la nueva consulta.
	 * @param query     Consulta SQL.
	 */
	final public function createQuery($dao, $queryName, $sql)
	{
		if ( !$dao )
			return(null);

		$statement = $this->hdb->prepare($sql);

		$this->namedQuery[$queryName][self::$QRY_DAO] = $dao;
		$this->namedQuery[$queryName][self::$QRY_STM] = $statement;

		return( $this->namedQuery[$queryName] );
	}

	/**
	 * Crea una consulta que se ejecutará -solamente- si
	 * se valida correctamente el token especificado.
	 *
	 * @param token     Objeto Token que se comprobará.
	 * @param dao       Objeto DAO relacionado con esta consulta.
	 * @param queryName Nombre de la nueva consulta.
	 * @param query     Consulta SQL.
	 */
	final public function createSecureQuery($token, $dao, $queryName, $sql)
	{
		$query = $this->createQuery($dao, $queryName, $sql);

		if ( !$query )
			return( null );

		$this->namedQuery[$queryName][self::$QRY_TOK] = $token;
	}

	/**
	 * Genera un array con los parámetros que se
	 * le pasarán a una consulta SQL.
	 *
	 * @param $parameters Lista de parámetros que se pasarán.
	 */
	final public function buildParameters( $parameters )
	{
		$sqlParams = array();

		$i = 1;

		// Convierte los parámetros a entidades SQL.
		//

		foreach( $parameters as $key => $value )
		{
			$sqlParams[":" . $i ] = $value;

			$i++;
		}

		return( $sqlParams );
	}

	/**
	 * Prepara una consulta para ser ejecutada.
	 *
	 * @param dao         Objeto DAO relacionado.
	 * @param queryName   Nombre de la consulta a ejecutar.
	 * @param arguments   Lista de parámetros que serán pasados a la consulta.
	 * @param modelObject Objeto del modelo al que se mapearán los resultados.
	 */
	final public function prepareStatement( $dao, $queryName, $arguments, $modelObject = null )
	{
		if ( !$this->hdb )
			return(null);

		if ( !is_array($arguments) )
			$arguments = array($arguments);

		$statement = $this->getNamedQuery( $dao, $queryName );
		$token     = $this->getQueryToken( $dao, $queryName );

		if ( !$statement ) {
			return(null);
		}

		// Verifica el token de seguridad.
		//
		if ( ($token instanceof Token) && !$token->assert() ) {

			return(null);
		}

		$prepared  = new PreparedStatement();

		$prepared->statement   = $statement;
		$prepared->parameters  = $this->buildParameters( $arguments  );
		$prepared->modelObject = $this->getTable( $dao, $modelObject );

		return( $prepared );
	}

	final public function execute( PreparedStatement $preparedStmt )
	{
		if ( !$preparedStmt ) {
			return(null);
		}

		// Ejecuta la consulta.
		//
		try {
			$preparedStmt->returnCode = $preparedStmt->statement->execute( $preparedStmt->parameters );
		}
		catch( PDOException $error )
		{
			$class = get_class( $this );

			throw new SQLException (
				$error->getMessage(),
				$error->getCode(),
				$error->errorInfo,
				$error->getTrace(),
				ClassLoader::getInstance()->getClassPath( $class ) . $class,
				$error->getLine()
			);
		}
		catch( Exception $error )
		{
			throw new UnhandledException (
				null,
				$error->getMessage(),
				$error->getCode(),
				$error->getLine()
			);
		}

		if ( !$preparedStmt->returnCode ) {
			return( false );  // Falló la consulta.
		}

		return( true );
	}

	/**
	 * Ejecuta una consulta contra la base de datos, y
	 * devuelve los resultados como objetos del modelo.
	 *
	 * @param $preparedStmt Objeto que contiene la consulta que se ejecutará.
	 */
	final public function fetch( PreparedStatement $preparedStmt )
	{
		if ( !$this->execute($preparedStmt) )
		{
			return( null );
		}

		// Obtiene los resultados.
		//
		$preparedStmt->resultMap = $this->mapFetchResultset( $preparedStmt );

		if ( count( $preparedStmt->resultMap ) == 0 )
		{
			$preparedStmt->resultMap = null;
		}
		elseif ( count( $preparedStmt->resultMap ) == 1 )
		{
			$preparedStmt->resultMap = $preparedStmt->resultMap[0];
		}

		return( $preparedStmt->resultMap );
	}

	final public function saveOrUpdate( PreparedStatement $preparedStmt )
	{
		if ( !$this->execute($preparedStmt) )
		{
			return( null );
		}

		return( $preparedStmt->returnCode );
	}

	final public function delete( PreparedStatement $preparedStmt )
	{
		if ( !$this->execute($preparedStmt) )
		{
			return( null );
		}

		return( true );
	}

	/**
	 * Convierte los resultados de una consulta SQL a los
	 * objetos de tabla vinculados con el DAO especificado.
	 *
	 * @param dao    Objeto DAO relacionado.
	 * @param result Respuesta de la consulta ejecutada con query();
	 */
	final protected function mapFetchResultset( PreparedStatement $preparedStmt )
	{
		$objects = $preparedStmt->statement->fetchAll( PDO::FETCH_OBJ );

		if ( !$preparedStmt->modelObject ) {
			return( $objects );
		}

		$modelObject = new $preparedStmt->modelObject();

		$fetchList = array();

		for( $i = 0; $i < count($objects); $i++ )
		{
			$fetchList[$i] = new DynamicObject();
			$fetchList[$i]->fromMergeObject( $modelObject, $objects[$i] );
		}

		return( $fetchList );
	}

	final protected function mapCountQuery(&$result)
	{
		if ( !is_array($result) || !is_array($result[0]) )
			return(false);

		$key = array_keys($result[0]);

		if ( $key[0] == "COUNT(*)" ) {
			$result = $result[0][ $key[0] ];

			return(true);
		}

		return(false);
	}
}
?>
